import dateutil.parser
from django.contrib import messages
from django.db import transaction
from django.db.models import Exists, Max, OuterRef, Subquery
from django.http import Http404, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect
from django.urls import reverse
from django.utils.functional import cached_property
from django.utils.timezone import is_aware, make_aware, now
from django.utils.translation import gettext_lazy as _
from django.views.generic import DeleteView, ListView
from pytz import UTC

from eventyay.base.channels import get_all_sales_channels
from eventyay.base.models import Checkin, Order, OrderPosition
from eventyay.base.models.checkin import CheckinList
from eventyay.base.signals import checkin_created
from eventyay.control.forms.checkin import CheckinListForm
from eventyay.control.forms.filter import CheckInFilterForm
from eventyay.control.permissions import EventPermissionRequiredMixin
from eventyay.control.views import CreateView, PaginationMixin, UpdateView
from eventyay.helpers.models import modelcopy


class CheckInListShow(EventPermissionRequiredMixin, PaginationMixin, ListView):
    model = Checkin
    context_object_name = 'entries'
    template_name = 'pretixcontrol/checkin/index.html'
    permission = 'can_view_orders'

    def get_queryset(self, filter=True):
        cqs = (
            Checkin.objects.filter(
                position_id=OuterRef('pk'),
                list_id=self.list.pk,
                type=Checkin.TYPE_ENTRY,
            )
            .order_by()
            .values('position_id')
            .annotate(m=Max('datetime'))
            .values('m')
        )
        cqs_exit = (
            Checkin.objects.filter(position_id=OuterRef('pk'), list_id=self.list.pk, type=Checkin.TYPE_EXIT)
            .order_by()
            .values('position_id')
            .annotate(m=Max('datetime'))
            .values('m')
        )

        qs = (
            OrderPosition.objects.filter(
                order__event=self.request.event,
                order__status__in=[Order.STATUS_PAID, Order.STATUS_PENDING]
                if self.list.include_pending
                else [Order.STATUS_PAID],
            )
            .annotate(
                last_entry=Subquery(cqs),
                last_exit=Subquery(cqs_exit),
                auto_checked_in=Exists(
                    Checkin.objects.filter(
                        position_id=OuterRef('pk'),
                        list_id=self.list.pk,
                        auto_checked_in=True,
                    )
                ),
            )
            .select_related('product', 'variation', 'order', 'addon_to')
        )
        if self.list.subevent:
            qs = qs.filter(subevent=self.list.subevent)

        if not self.list.all_products:
            qs = qs.filter(product__in=self.list.limit_products.values_list('id', flat=True))

        if filter and self.filter_form.is_valid():
            qs = self.filter_form.filter_qs(qs)

        return qs

    @cached_property
    def filter_form(self):
        return CheckInFilterForm(data=self.request.GET, event=self.request.event, list=self.list)

    def dispatch(self, request, *args, **kwargs):
        self.list = get_object_or_404(self.request.event.checkin_lists.all(), pk=kwargs.get('list'))
        return super().dispatch(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        ctx['checkinlist'] = self.list
        if self.request.event.has_subevents:
            ctx['seats'] = (
                self.list.subevent.seating_plan_id
                if self.list.subevent
                else self.request.event.subevents.filter(seating_plan__isnull=False).exists()
            )
        else:
            ctx['seats'] = self.request.event.seating_plan_id
        ctx['filter_form'] = self.filter_form
        for e in ctx['entries']:
            if e.last_entry:
                if isinstance(e.last_entry, str):
                    # Apparently only happens on SQLite
                    e.last_entry_aware = make_aware(dateutil.parser.parse(e.last_entry), UTC)
                elif not is_aware(e.last_entry):
                    # Apparently only happens on MySQL
                    e.last_entry_aware = make_aware(e.last_entry, UTC)
                else:
                    # This would be correct, so guess on which database it works… Yes, it's PostgreSQL.
                    e.last_entry_aware = e.last_entry
            if e.last_exit:
                if isinstance(e.last_exit, str):
                    # Apparently only happens on SQLite
                    e.last_exit_aware = make_aware(dateutil.parser.parse(e.last_exit), UTC)
                elif not is_aware(e.last_exit):
                    # Apparently only happens on MySQL
                    e.last_exit_aware = make_aware(e.last_exit, UTC)
                else:
                    # This would be correct, so guess on which database it works… Yes, it's PostgreSQL.
                    e.last_exit_aware = e.last_exit
        return ctx

    def post(self, request, *args, **kwargs):
        if 'can_change_orders' not in request.eventpermset:
            messages.error(request, _('You do not have permission to perform this action.'))
            return redirect(
                reverse(
                    'control:event.orders.checkins',
                    kwargs={
                        'event': self.request.event.slug,
                        'organizer': self.request.event.organizer.slug,
                    },
                )
                + '?'
                + request.GET.urlencode()
            )

        positions = self.get_queryset(filter=False).filter(pk__in=request.POST.getlist('checkin'))

        if request.POST.get('revert') == 'true':
            for op in positions:
                if op.order.status == Order.STATUS_PAID or (
                    self.list.include_pending and op.order.status == Order.STATUS_PENDING
                ):
                    Checkin.objects.filter(position=op, list=self.list).delete()
                    op.order.log_action(
                        'pretix.event.checkin.reverted',
                        data={
                            'position': op.id,
                            'positionid': op.positionid,
                            'list': self.list.pk,
                            'web': True,
                        },
                        user=request.user,
                    )
                    op.order.touch()

            messages.success(request, _('The selected check-ins have been reverted.'))
        else:
            for op in positions:
                if op.order.status == Order.STATUS_PAID or (
                    self.list.include_pending and op.order.status == Order.STATUS_PENDING
                ):
                    t = Checkin.TYPE_EXIT if request.POST.get('checkout') == 'true' else Checkin.TYPE_ENTRY

                    lci = op.checkins.filter(list=self.list).first()
                    if (
                        self.list.allow_multiple_entries
                        or t != Checkin.TYPE_ENTRY
                        or (lci and lci.type != Checkin.TYPE_ENTRY)
                    ):
                        ci = Checkin.objects.create(position=op, list=self.list, datetime=now(), type=t)
                        created = True
                    else:
                        try:
                            ci, created = Checkin.objects.get_or_create(
                                position=op,
                                list=self.list,
                                defaults={
                                    'datetime': now(),
                                },
                            )
                        except Checkin.MultipleObjectsReturned:
                            ci, created = (
                                Checkin.objects.filter(position=op, list=self.list).first(),
                                False,
                            )

                    op.order.log_action(
                        'pretix.event.checkin',
                        data={
                            'position': op.id,
                            'positionid': op.positionid,
                            'first': created,
                            'forced': False,
                            'datetime': now(),
                            'type': t,
                            'list': self.list.pk,
                            'web': True,
                        },
                        user=request.user,
                    )
                    checkin_created.send(op.order.event, checkin=ci)

            messages.success(request, _('The selected tickets have been marked as checked in.'))

        return redirect(
            reverse(
                'control:event.orders.checkinlists.show',
                kwargs={
                    'event': self.request.event.slug,
                    'organizer': self.request.event.organizer.slug,
                    'list': self.list.pk,
                },
            )
            + '?'
            + request.GET.urlencode()
        )


class CheckinListList(EventPermissionRequiredMixin, PaginationMixin, ListView):
    model = CheckinList
    context_object_name = 'checkinlists'
    permission = 'can_view_orders'
    template_name = 'pretixcontrol/checkin/lists.html'

    def get_queryset(self):
        qs = self.request.event.checkin_lists.select_related('subevent').prefetch_related('limit_products')

        if self.request.GET.get('subevent', '') != '':
            s = self.request.GET.get('subevent', '')
            qs = qs.filter(subevent_id=s)
        return qs

    def get_context_data(self, **kwargs):
        ctx = super().get_context_data(**kwargs)
        clists = list(ctx['checkinlists'])
        sales_channels = get_all_sales_channels()

        for cl in clists:
            if cl.subevent:
                cl.subevent.event = self.request.event  # re-use same event object to make sure settings are cached
            cl.auto_checkin_sales_channels = [sales_channels[channel] for channel in cl.auto_checkin_sales_channels]
        ctx['checkinlists'] = clists

        ctx['can_change_organizer_settings'] = self.request.user.has_organizer_permission(
            self.request.organizer, 'can_change_organizer_settings', self.request
        )

        return ctx


class CheckinListCreate(EventPermissionRequiredMixin, CreateView):
    model = CheckinList
    form_class = CheckinListForm
    template_name = 'pretixcontrol/checkin/list_edit.html'
    permission = 'can_change_event_settings'
    context_object_name = 'checkinlist'

    def dispatch(self, request, *args, **kwargs):
        r = super().dispatch(request, *args, **kwargs)
        r['Content-Security-Policy'] = "script-src 'unsafe-eval'"
        return r

    @cached_property
    def copy_from(self):
        if self.request.GET.get('copy_from') and not getattr(self, 'object', None):
            try:
                return self.request.event.checkin_lists.get(pk=self.request.GET.get('copy_from'))
            except CheckinList.DoesNotExist:
                pass

    def get_form_kwargs(self):
        kwargs = super().get_form_kwargs()

        if self.copy_from:
            i = modelcopy(self.copy_from)
            i.pk = None
            kwargs['instance'] = i
        else:
            kwargs['instance'] = CheckinList(event=self.request.event)
        return kwargs

    def get_success_url(self) -> str:
        return reverse(
            'control:event.orders.checkinlists',
            kwargs={
                'organizer': self.request.event.organizer.slug,
                'event': self.request.event.slug,
            },
        )

    @transaction.atomic
    def form_valid(self, form):
        form.instance.event = self.request.event
        messages.success(self.request, _('The new check-in list has been created.'))
        ret = super().form_valid(form)
        form.instance.log_action(
            'pretix.event.checkinlist.added',
            user=self.request.user,
            data=dict(form.cleaned_data),
        )
        return ret

    def form_invalid(self, form):
        messages.error(self.request, _('We could not save your changes. See below for details.'))
        return super().form_invalid(form)


class CheckinListUpdate(EventPermissionRequiredMixin, UpdateView):
    model = CheckinList
    form_class = CheckinListForm
    template_name = 'pretixcontrol/checkin/list_edit.html'
    permission = 'can_change_event_settings'
    context_object_name = 'checkinlist'

    def dispatch(self, request, *args, **kwargs):
        r = super().dispatch(request, *args, **kwargs)
        r['Content-Security-Policy'] = "script-src 'unsafe-eval'"
        return r

    def get_object(self, queryset=None) -> CheckinList:
        try:
            return self.request.event.checkin_lists.get(id=self.kwargs['list'])
        except CheckinList.DoesNotExist:
            raise Http404(_('The requested list does not exist.'))

    @transaction.atomic
    def form_valid(self, form):
        messages.success(self.request, _('Your changes have been saved.'))
        if form.has_changed():
            self.object.log_action(
                'pretix.event.checkinlist.changed',
                user=self.request.user,
                data={k: form.cleaned_data.get(k) for k in form.changed_data},
            )
        return super().form_valid(form)

    def get_success_url(self) -> str:
        return reverse(
            'control:event.orders.checkinlists.edit',
            kwargs={
                'organizer': self.request.event.organizer.slug,
                'event': self.request.event.slug,
                'list': self.object.pk,
            },
        )

    def form_invalid(self, form):
        messages.error(self.request, _('We could not save your changes. See below for details.'))
        return super().form_invalid(form)


class CheckinListDelete(EventPermissionRequiredMixin, DeleteView):
    model = CheckinList
    template_name = 'pretixcontrol/checkin/list_delete.html'
    permission = 'can_change_event_settings'
    context_object_name = 'checkinlist'

    def get_object(self, queryset=None) -> CheckinList:
        try:
            return self.request.event.checkin_lists.get(id=self.kwargs['list'])
        except CheckinList.DoesNotExist:
            raise Http404(_('The requested list does not exist.'))

    @transaction.atomic
    def form_valid(self, form):
        self.object = self.get_object()
        success_url = self.get_success_url()
        self.object.checkins.all().delete()
        self.object.log_action(action='pretix.event.checkinlists.deleted', user=self.request.user)
        self.object.delete()
        messages.success(self.request, _('The selected list has been deleted.'))
        return HttpResponseRedirect(success_url)

    def get_success_url(self) -> str:
        return reverse(
            'control:event.orders.checkinlists',
            kwargs={
                'organizer': self.request.event.organizer.slug,
                'event': self.request.event.slug,
            },
        )
